{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"../common/rfsoc_book_banner.jpg\" alt=\"University of Strathclyde\" align=\"left\">"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Notebook Set A\n",
    "\n",
    "---\n",
    "\n",
    "## 02 - Visualisation and Analysis\n",
    "In this notebook, we will introduce the NumPy, SciPy, MatplotLib, Plotly, and ipywidgets libraries. Each library has their own capabilities and these will be described with a brief code example.\n",
    "\n",
    "## Table of Contents\n",
    "* [1. Introduction](#introduction)\n",
    "* [2. NumPy](#section)\n",
    "* [3. Pandas](#pandas)\n",
    "* [4. SciPy](#scipy)\n",
    "* [5. MatplotLib](#matplotlib)\n",
    "* [6. Plotly](#plotly)\n",
    "* [7. ipywidgets](#ipywidgets)\n",
    "* [8. Conclusion](#conclusion)\n",
    "\n",
    "## References\n",
    "* [1] - [NumPy, \"NumPy Website\", webpage](https://numpy.org/)\n",
    "* [2] - [Pandas, \"Pandas Website\", webpage](https://pandas.pydata.org/)\n",
    "* [3] - [SciPy, \"SciPy Website\", webpage](https://scipy.org/)\n",
    "* [4] - [MatplotLib, \"MatplotLib Website\", webpage](https://matplotlib.org/)\n",
    "* [5] - [Plotly, \"Plotly Website\", webpage.](https://plotly.com/)\n",
    "* [6] - [jupyter-widgets, “GitHub Source Code Repository for the IPywidgets Python Library,” webpage.](https://github.com/jupyter-widgets/ipywidgets)\n",
    "\n",
    "## Revision\n",
    "\n",
    "* **v1.0** | 23/01/23 | *First Revision*\n",
    "\n",
    "---\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Introduction <a class=\"anchor\" id=\"introduction\"></a>\n",
    "JupyterLab can leverage several Python libraries for plotting, evaluating, and manipulating data. Many of these libraries will be demonstrated in this notebook. If you would like to learn more about a particular library, please see the associated webpage for further information."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. NumPy <a class=\"anchor\" id=\"numpy\"></a>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "NumPy is arguably one of the most commonly used Python libraries for mathematical functions and multi-dimensional matrix operations. Users of the this library often denote it as `np` when importing it into software projects, as below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When using the NumPy library, you will most likely use the multi-dimensional array object denoted as `np.ndarray`, or `np.array` for short. You can create a multi-dimensional array as shown in the following code cell. Notice that defining the shape of the array is essential and is given as a tuple."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "n = 5 # rows\n",
    "m = 5 # columns\n",
    "\n",
    "A = np.ndarray(shape=(n, m))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There are several important properties of the `np.ndarray` listed below.\n",
    "\n",
    "* `ndim` — *The number of dimensions the array contains.*\n",
    "* `shape` — *The shape of a $n\\times m$ `np.ndarray` as a tuple.*\n",
    "* `size` — *The total number of elements in the array, which is equal to the product of $n \\cdot m$.*\n",
    "* `dtype` — *The data type used by the elements in the array e.g. int, single, float, or NumPy data types.*\n",
    "\n",
    "The data buffer of the array can be returned using `ndarray.data`.\n",
    "\n",
    "An simple example showcasing these properties is given below. The example uses the NumPy random module to generate data elements for manipulation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "A = np.random.normal(0, 1, (4, 4))\n",
    "A"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "A.ndim # Number of dimensions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "A.shape # Shape of the multi-dimensional array"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "A.size # Total number of elements in the array"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "A.dtype # Data-type of the elements stored in the array"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Multi-dimensional arrays can be created in a variety of different ways. Above, we chose to use the NumPy random module to create a normal distribution of values. We can also initialise an array of zero values, one values, and incrementing values within a range."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "np.zeros(shape=(4, 4))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "np.ones(shape=(4, 4))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "A = np.arange(0, 16, 1)\n",
    "A.reshape(4, 4) # Arrays can be reshaped"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "NumPy also has a variety of mathematical functions that you can use, which include the following:\n",
    "* `np.sin` — Generates an array of sine samples,\n",
    "* `np.cos` — Generates an array of cosine samples,\n",
    "* `np.tan` — Generates an array of tangent samples,\n",
    "* `np.log10` — Performs a logarithm to the base 10,\n",
    "* `np.log2` — Performs a logarithm to the base 2,\n",
    "* `np.exp` — Implements an exponential function,\n",
    "* `np.sqrt` — Implements an exponential function,\n",
    "* ... and much, much more.\n",
    "\n",
    "We can generate a sine wave below, for use later with Pandas and MatplotLib."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fs = 1000 # Hz\n",
    "ts = 1/fs # Seconds\n",
    "fd = 20   # Hz\n",
    "N  = 100  # Samples\n",
    "n  = np.arange(0, N)\n",
    "sine_wave = np.sin(2*np.pi*n*fd*ts)\n",
    "sine_wave"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We are now finished our brief exploration of the NumPy library. The NumPy reference is available in [1] if you would like to know more."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. Pandas <a class=\"anchor\" id=\"pandas\"></a>\n",
    "Pandas is a fast and powerful data manipulation and analysis library, which has been optimised using C code. Developers typically import the Pandas library by denoting it as `pd`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We will begin by discussing the most used object in the Pandas library, which is the DataFrame (often abbreviated as `df`). A DataFrame is a 2-dimensional array. Let us create a DataFrame of the NumPy sine wave that we generated before."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data = {\n",
    "    \"time\" : n*ts,\n",
    "    \"sine\" : sine_wave\n",
    "}\n",
    "\n",
    "df = pd.DataFrame(data)\n",
    "\n",
    "df"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As shown, the Pandas DataFrame conveniently presents the stored data in a table format for inspection. The table row is given on the left, while the data is presented in the subsequent columns to the right.\n",
    "\n",
    "We can locate a specific row using the index, as below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.loc[[42]]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Several rows can be returned by adding more indices to the list."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.loc[[42, 64, 96]]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can select one column by calling it directly using the DataFrame."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.sine"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Lastly, Pandas is able to read and write DataFrames to and from comma separated files (csv). For instance, we can write our DataFrame to a csv, as below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.to_csv('sine_data.csv')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now the DataFrame can be retrieved."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.read_csv('sine_data.csv')\n",
    "df"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We have only briefly covered some of the features of the Pandas library. If you would like to learn more, see the reference given in [2]."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. SciPy <a class=\"anchor\" id=\"scipy\"></a>\n",
    "SciPy is an enormous Python library for scientific computing tasks. It contains many different modules for various applications. Importing SciPy into a project requires that you know the module you would like to use. For example, we can import the constants module, as below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from scipy import constants"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There are several different constants to choose from. The cell below returns the time for a minute, hour, day, week, and year in seconds."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "time = ['minute', 'hour', 'day', 'week', 'year']\n",
    "for t in time:\n",
    "    print(t, getattr(constants, t))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There is also an optimisation module that consists of methods that obtain the minimum value, or root, of a function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from scipy import optimize"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For example, we can obtain the minimum of $y^2$ with an initial guess of 2."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def f(y):\n",
    "    return y**2\n",
    "\n",
    "optimize.fmin(f, 2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "SciPy contains a vast amount of other useful functions and features including integration, interpolation, decimation, spatial processing, optimisation, Fourier analysis, and much more. If you would like to learn more, see the reference given in [3]."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. MatplotLib <a class=\"anchor\" id=\"matplotlib\"></a>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So far, we have used Python libraries to manipulate or generate data. A useful library for visualising and plotting data in Python is MatplotLib, which contains a variety of different modules for plotting. We will focus primarily on the pyplot module in this notebook. The pyplot module is typically denoted as `plt` and is imported as shown below. Notice the use of the magic command `%matplotlib inline`, which allows us to plot directly to the Jupyter Notebook."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A very simple example of demonstrating the plotting capabilities of pyplot, is to plot the sine wave we generated earlier."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.plot(n*ts, sine_wave)\n",
    "plt.ylabel('Amplitude')\n",
    "plt.xlabel('Time (s)')\n",
    "plt.title('Sine Wave')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We successfully plotted a sine wave above. There are a variety of other plots that MatplotLib can provide. For instance, a stem plot can also be plotted below. Later in a future notebook, we will use 3-dimensional plots to visualise data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.stem(n*ts, sine_wave, use_line_collection=True)\n",
    "plt.ylabel('Amplitude')\n",
    "plt.xlabel('Time (s)')\n",
    "plt.title('Sine Wave Stem Plot')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "See [4] if you would like to learn more about MatplotLib."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6. Plotly <a class=\"anchor\" id=\"plotly\"></a>\n",
    "Plotly also allows the user to plot and visualise data using Python by providing a simple set of classes and methods for creating figures and plots quickly. By default, plots generated using Plotly are interactive, which means you are able to hover over data points and zoom, scale, and pan around the plot.\n",
    "\n",
    "Plotly users have a wide range of different modules to choose from. In this notebook, we will use the Graphics Objects module, which is typically imported as `go`, as shown in the code cell below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import plotly.graph_objs as go"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Similar to before, we can plot the sine wave against time using the Plotly Graphics Objects Scatter class. There are many different plots to select from, but the Scatter class provides the most useful features for our purposes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "go.Figure(data = [go.Scatter(x = n*ts, y = sine_wave)],\n",
    "          layout = {\n",
    "              'title' : 'Sine Wave',\n",
    "              'yaxis' : {\n",
    "                  'title' : 'Amplitude'\n",
    "              },\n",
    "              'xaxis' : {\n",
    "                  'title' : 'Time (s)'\n",
    "              }})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You should be able to interact with the plot directly by hovering over the trace, or using the buttons at the top of the plot. If you would like to learn more about Plotly, see the reference material in [5]."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 7. ipywidgets <a class=\"anchor\" id=\"ipywidgets\"></a>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The final library we will investigate in this notebook is ipywidgets. This Python library allows users and developers to generate their own buttons, switches, dropdowns, sliders, and other interactive widgets for their application. Users will normally import ipywidgets as `ipw`, which can be seen below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import ipywidgets as ipw"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can generate a range of different widgets using ipywidgets. However, in this notebook, we will limit ourselves to just one for demonstration purposes. Below, is a code cell that will allow us to generate a slider widget. Every time the slider is changed, the slider's value is printed to the notebook."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def slider_callback(value):\n",
    "    print('Current Value: ', value)\n",
    "\n",
    "slider = ipw.IntSlider(min=0, max=7, step=1)\n",
    "ipw.interact(slider_callback, value=slider);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Try changing the slider value to see the new value printed to the notebook. The ipywidgets library is an excellent resource for generating widgets for an application. If you would like to learn more, see the reference material in [6]."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 8. Conclusion\n",
    "This notebook has provided a brief introduction to several useful Python libraries. These included NumPy, Pandas, SciPy, MatplotLib, Plotly and ipywidgets.\n",
    "\n",
    "The next notebook dives into the PYNQ framework."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "[⬅️ Previous Notebook](01_jupyter_lab.ipynb) || [Next Notebook 🚀](03_pynq_introduction.ipynb)\n",
    "\n",
    "Copyright © 2023 Strathclyde Academic Media\n",
    "\n",
    "---\n",
    "---"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
